namespace ScrumSprintMonitor.UI.Wpf.PostSharp
{
	#region #using Directives

	using System;
	using System.Linq;
	using System.Reflection;

	using global::PostSharp.Extensibility;
	using global::PostSharp.Laos;

	using ViewModels;

	#endregion

	/// <summary>
	/// Custom attribute that, when applied on a type (designated <i>target type</i>), adds GuardDisposed calls to public methods.
	/// </summary>
	[MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)]
	[Serializable]
	public sealed class GuardDisposedAttribute : CompoundAspect
	{
		[NonSerialized] private int aspectPriority;

		/// <summary>
		/// Gets or sets the priority of the property-level aspect.
		/// </summary>
		/// <remarks>
		/// Give a large number to have the event raisen after any other
		/// on-success aspect on the properties of this type. The default value
		/// is 0.
		/// </remarks>
		public int AspectPriority
		{
			get { return this.aspectPriority; }
			set { this.aspectPriority = value; }
		}

		/// <summary>
		/// Method called at compile time to get individual aspects required by the current compound
		/// aspect.
		/// </summary>
		/// <param name="targetElement">Metadata element (<see cref="Type"/> in our case) to which
		/// the current custom attribute instance is applied.</param>
		/// <param name="collection">Collection of aspects to which individual aspects should be
		/// added.</param>
		public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection)
		{
			// Get the target type.
			var targetType = (Type) targetElement;
			if (!typeof(ViewModelBase).IsAssignableFrom(targetType))
			{
				throw new ArgumentException(string.Format("Cannot apply the {0} aspect to {1}. It can can only be applied to ViewModelBase-derived classes.",
				                                          GetType().Name, targetType.Name));
			}

			var publicMethods = targetType.UnderlyingSystemType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
			var methods = publicMethods.Where(m => m != null);

			// Add a OnMethodBoundaryAspect on each public non-static property/method.
			foreach (var method in methods.Where(m => !m.IsStatic && !m.IsAbstract && m.DeclaringType == targetType))
			{
				collection.AddAspect(method, new OnPublicMethodCallSubAspect(this));
			}
		}

		#region Nested type: OnPublicMethodCallSubAspect

		/// <summary>
		/// Implementation of <see cref="OnMethodBoundaryAspect"/> that calls ViewModelBase.GuardDisposed()
		/// when a public method is invoked.
		/// </summary>
		[Serializable]
		internal class OnPublicMethodCallSubAspect : OnMethodBoundaryAspect
		{
			/// <summary>
			/// Initializes a new <see cref="OnPublicMethodCallSubAspect"/>.
			/// </summary>
			/// <param name="parent">Parent <see cref="GuardDisposedAttribute"/>.</param>
			public OnPublicMethodCallSubAspect(GuardDisposedAttribute parent)
			{
				AspectPriority = parent.AspectPriority;
			}

			public override void OnEntry(MethodExecutionEventArgs eventArgs)
			{
				var viewModelBase = eventArgs.Instance as ViewModelBase;

				if (viewModelBase != null)
				{
					viewModelBase.GuardDisposed();
				}

				base.OnEntry(eventArgs);
			}
		}

		#endregion
	}
}